<?php

namespace Luracast\Restler\Format;

use Exception;
use Illuminate\Events\Dispatcher;
use Illuminate\Filesystem\Filesystem;
use Illuminate\View\Compilers\BladeCompiler;
use Illuminate\View\Engines\CompilerEngine;
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Factory;
use Illuminate\View\FileViewFinder;
use Illuminate\View\View;
use Luracast\Restler\Data\ApiMethodInfo;
use Luracast\Restler\Data\Object;
use Luracast\Restler\Defaults;
use Luracast\Restler\RestException;
use Luracast\Restler\Restler;
use Luracast\Restler\Scope;
use Luracast\Restler\UI\Nav;
use Luracast\Restler\Util;

/**
 * Html template format
 *
 * @category Framework
 * @package Restler
 * @subpackage format
 * @author R.Arul Kumaran <arul@luracast.com>
 * @copyright 2010 Luracast
 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
 * @link http://luracast.com/products/restler/
 * @version 3.0.0rc5
 */
class HtmlFormat extends Format {
	public static $mime = 'text/html';
	public static $extension = 'html';
	public static $view;
	public static $errorView = 'debug.php';
	public static $template = 'php';
	public static $handleSession = true;
	public static $useSmartViews = true;
	/**
	 *
	 * @var null|string defaults to template named folder in Defaults::$cacheDirectory
	 */
	public static $cacheDirectory = null;
	/**
	 *
	 * @var array global key value pair to be supplied to the templates. All
	 *      keys added here will be available as a variable inside the template
	 */
	public static $data = array ();
	/**
	 *
	 * @var string set it to the location of your the view files. Defaults to
	 *      views folder which is same level as vendor directory.
	 */
	public static $viewPath;
	/**
	 *
	 * @var array template and its custom extension key value pair
	 */
	public static $customTemplateExtensions = array (
			'blade' => 'blade.php' 
	);
	/**
	 *
	 * @var bool used internally for error handling
	 */
	protected static $parseViewMetadata = true;
	/**
	 *
	 * @var Restler;
	 */
	public $restler;
	public function __construct() {
		// ============ SESSION MANAGEMENT =============//
		if (static::$handleSession) {
			if (session_start () && isset ( $_SESSION ['flash'] )) {
				static::$data ['flash'] = $_SESSION ['flash'];
				unset ( $_SESSION ['flash'] );
			}
		}
		if (! static::$viewPath) {
			$array = explode ( 'vendor', __DIR__, 2 );
			static::$viewPath = $array [0] . 'views';
		}
	}
	public static function blade(array $data, $debug = true) {
		if (! class_exists ( '\Illuminate\View\View', true ))
			throw new RestException ( 500, 'Blade templates require laravel view classes to be installed using `composer install`' );
		$resolver = new EngineResolver ();
		$files = new Filesystem ();
		$compiler = new BladeCompiler ( $files, static::$cacheDirectory );
		$engine = new CompilerEngine ( $compiler );
		$resolver->register ( 'blade', function () use($engine) {
			return $engine;
		} );
		
		/**
		 * @var Restler $restler
		 */
		$restler = Scope::get ( 'Restler' );
		
		// Lets expose shortcuts for our classes
		spl_autoload_register ( function ($className) use($restler) {
			if (isset ( $restler->apiMethodInfo->metadata ['scope'] [$className] )) {
				return class_alias ( $restler->apiMethodInfo->metadata ['scope'] [$className], $className );
			}
			if (isset ( Scope::$classAliases [$className] )) {
				return class_alias ( Scope::$classAliases [$className], $className );
			}
			return false;
		}, true, true );
		
		$viewFinder = new FileViewFinder ( $files, array (
				static::$viewPath 
		) );
		$factory = new Factory ( $resolver, $viewFinder, new Dispatcher () );
		$path = $viewFinder->find ( self::$view );
		$view = new View ( $factory, $engine, self::$view, $path, $data );
		$factory->callCreator ( $view );
		return $view->render ();
	}
	public static function twig(array $data, $debug = true) {
		if (! class_exists ( '\Twig_Environment', true ))
			throw new RestException ( 500, 'Twig templates require twig classes to be installed using `composer install`' );
		$loader = new \Twig_Loader_Filesystem ( static::$viewPath );
		$twig = new \Twig_Environment ( $loader, array (
				'cache' => static::$cacheDirectory,
				'debug' => $debug,
				'use_strict_variables' => $debug 
		) );
		if ($debug)
			$twig->addExtension ( new \Twig_Extension_Debug () );
		
		$twig->addFunction ( new \Twig_SimpleFunction ( 'form', 'Luracast\Restler\UI\Forms::get', array (
				'is_safe' => array (
						'html' 
				) 
		) ) );
		$twig->addFunction ( new \Twig_SimpleFunction ( 'form_key', 'Luracast\Restler\UI\Forms::key' ) );
		$twig->addFunction ( new \Twig_SimpleFunction ( 'nav', 'Luracast\Restler\UI\Nav::get' ) );
		
		$twig->registerUndefinedFunctionCallback ( function ($name) {
			if (isset ( HtmlFormat::$data [$name] ) && is_callable ( HtmlFormat::$data [$name] )) {
				return new \Twig_SimpleFunction ( $name, HtmlFormat::$data [$name] );
			}
			return false;
		} );
		
		$template = $twig->loadTemplate ( static::getViewFile () );
		return $template->render ( $data );
	}
	public static function handlebar(array $data, $debug = true) {
		return static::mustache ( $data, $debug );
	}
	public static function mustache(array $data, $debug = true) {
		if (! class_exists ( '\Mustache_Engine', true ))
			throw new RestException ( 500, 'Mustache/Handlebar templates require mustache classes ' . 'to be installed using `composer install`' );
		if (! isset ( $data ['nav'] ))
			$data ['nav'] = array_values ( Nav::get () );
		$options = array (
				'loader' => new \Mustache_Loader_FilesystemLoader ( static::$viewPath, array (
						'extension' => static::getViewExtension () 
				) ),
				'helpers' => array (
						'form' => function ($text, \Mustache_LambdaHelper $m) {
							$params = explode ( ',', $m->render ( $text ) );
							return call_user_func_array ( 'Luracast\Restler\UI\Forms::get', $params );
						} 
				) 
		);
		if (! $debug)
			$options ['cache'] = static::$cacheDirectory;
		$m = new \Mustache_Engine ( $options );
		return $m->render ( static::getViewFile (), $data );
	}
	public static function php(array $data, $debug = true) {
		if (static::$view == 'debug')
			static::$viewPath = dirname ( __DIR__ ) . '/views';
		$view = static::getViewFile ( true );
		
		if (! is_readable ( $view )) {
			throw new RestException ( 500, "view file `$view` is not readable. " . 'Check for file presence and file permissions' );
		}
		
		$path = static::$viewPath . DIRECTORY_SEPARATOR;
		$template = function ($view) use($data, $path) {
			$form = function () {
				return call_user_func_array ( 'Luracast\Restler\UI\Forms::get', func_get_args () );
			};
			if (! isset ( $data ['form'] ))
				$data ['form'] = $form;
			$nav = function () {
				return call_user_func_array ( 'Luracast\Restler\UI\Nav::get', func_get_args () );
			};
			if (! isset ( $data ['nav'] ))
				$data ['nav'] = $nav;
			
			$_ = function () use($data, $path) {
				extract ( $data );
				$args = func_get_args ();
				$task = array_shift ( $args );
				switch ($task) {
					case 'require' :
					case 'include' :
						$file = $path . $args [0];
						if (is_readable ( $file )) {
							if (isset ( $args [1] ) && ($arrays = Util::nestedValue ( $data, $args [1] ))) {
								$str = '';
								foreach ( $arrays as $arr ) {
									extract ( $arr );
									$str .= include $file;
								}
								return $str;
							} else {
								return include $file;
							}
						}
						break;
					case 'if' :
						if (count ( $args ) < 2)
							$args [1] = '';
						if (count ( $args ) < 3)
							$args [2] = '';
						return $args [0] ? $args [1] : $args [2];
						break;
					default :
						if (isset ( $data [$task] ) && is_callable ( $data [$task] ))
							return call_user_func_array ( $data [$task], $args );
				}
				return '';
			};
			extract ( $data );
			return @include $view;
		};
		$value = $template ( $view );
		if (is_string ( $value ))
			return $value;
	}
	
	/**
	 * Encode the given data in the format
	 *
	 * @param array $data
	 *        	resulting data that needs to
	 *        	be encoded in the given format
	 * @param boolean $humanReadable
	 *        	set to TRUE when restler
	 *        	is not running in production mode.
	 *        	Formatter has to make the encoded
	 *        	output more human readable
	 *        	
	 * @throws \Exception
	 * @return string encoded string
	 */
	public function encode($data, $humanReadable = false) {
		if (! is_readable ( static::$viewPath )) {
			throw new \Exception ( 'The views directory `' . self::$viewPath . '` should exist with read permission.' );
		}
		static::$data ['basePath'] = dirname ( $_SERVER ['SCRIPT_NAME'] );
		static::$data ['baseUrl'] = $this->restler->getBaseUrl ();
		static::$data ['currentPath'] = $this->restler->url;
		
		try {
			$exception = $this->restler->exception;
			$success = is_null ( $exception );
			$error = $success ? null : $exception->getMessage ();
			$data = array (
					'response' => Object::toArray ( $data ),
					'stages' => $this->restler->getEvents (),
					'success' => $success,
					'error' => $error 
			);
			$info = $data ['api'] = $this->restler->apiMethodInfo;
			$metadata = Util::nestedValue ( $this->restler, 'apiMethodInfo', 'metadata' );
			$view = $success ? 'view' : 'errorView';
			$value = false;
			if (static::$parseViewMetadata && isset ( $metadata [$view] )) {
				if (is_array ( $metadata [$view] )) {
					self::$view = $metadata [$view] ['description'];
					$value = Util::nestedValue ( $metadata [$view], 'properties', 'value' );
				} else {
					self::$view = $metadata [$view];
				}
			} elseif (! self::$view) {
				$file = static::$viewPath . '/' . $this->restler->url . '.' . static::getViewExtension ();
				self::$view = static::$useSmartViews && is_readable ( $file ) ? $this->restler->url : static::$errorView;
			}
			if (isset ( $metadata ['param'] ) && (! $value || 0 === strpos ( $value, 'request' ))) {
				$params = $metadata ['param'];
				foreach ( $params as $index => &$param ) {
					$index = intval ( $index );
					if (is_numeric ( $index )) {
						$param ['value'] = $this->restler->apiMethodInfo->parameters [$index];
					}
				}
				$data ['request'] ['parameters'] = $params;
			}
			if ($value) {
				$data = Util::nestedValue ( $data, explode ( '.', $value ) );
			}
			$data += static::$data;
			if (false === ($i = strrpos ( self::$view, '.' ))) {
				$template = self::$template;
			} else {
				self::$template = $template = substr ( self::$view, $i + 1 );
				self::$view = substr ( self::$view, 0, $i );
			}
			if (! static::$cacheDirectory) {
				static::$cacheDirectory = Defaults::$cacheDirectory . DIRECTORY_SEPARATOR . $template;
				if (! file_exists ( static::$cacheDirectory )) {
					if (! mkdir ( static::$cacheDirectory )) {
						throw new RestException ( 500, 'Unable to create cache directory `' . static::$cacheDirectory . '`' );
					}
				}
			}
			if (method_exists ( $class = get_called_class (), $template )) {
				return call_user_func ( "$class::$template", $data, $humanReadable );
			}
			throw new RestException ( 500, "Unsupported template system `$template`" );
		} catch ( Exception $e ) {
			static::$parseViewMetadata = false;
			$this->reset ();
			throw $e;
		}
	}
	public static function getViewExtension() {
		return isset ( static::$customTemplateExtensions [static::$template] ) ? static::$customTemplateExtensions [static::$template] : static::$template;
	}
	public static function getViewFile($fullPath = false, $includeExtension = true) {
		$v = $fullPath ? static::$viewPath . '/' : '';
		$v .= static::$view;
		if ($includeExtension)
			$v .= '.' . static::getViewExtension ();
		return $v;
	}
	private function reset() {
		static::$mime = 'text/html';
		static::$extension = 'html';
		static::$view = 'debug';
		static::$template = 'php';
	}
	
	/**
	 * Decode the given data from the format
	 *
	 * @param string $data
	 *        	data sent from client to
	 *        	the api in the given format.
	 *        	
	 * @return array associative array of the parsed data
	 *        
	 * @throws RestException
	 */
	public function decode($data) {
		throw new RestException ( 500, 'HtmlFormat is write only' );
	}
	
	/**
	 *
	 * @return bool false as HTML format is write only
	 */
	public function isReadable() {
		return false;
	}
	
	/**
	 * Get MIME type => Extension mappings as an associative array
	 *
	 * @return array list of mime strings for the format
	 * @example array('application/json'=>'json');
	 */
	public function getMIMEMap() {
		return array (
				static::$mime => static::$extension 
		);
	}
	
	/**
	 * Set the selected MIME type
	 *
	 * @param string $mime
	 *        	MIME type
	 */
	public function setMIME($mime) {
		static::$mime = $mime;
	}
	
	/**
	 * Get selected MIME type
	 */
	public function getMIME() {
		return static::$mime;
	}
	
	/**
	 * Get the selected file extension
	 *
	 * @return string file extension
	 */
	public function getExtension() {
		return static::$extension;
	}
	
	/**
	 * Set the selected file extension
	 *
	 * @param string $extension
	 *        	file extension
	 */
	public function setExtension($extension) {
		static::$extension = $extension;
	}
}
